# -*- coding: utf-8 -*-
# GUI Application automation and testing library
# Copyright (C) 2006-2018 Mark Mc Mahon and Contributors
# https://github.com/pywinauto/pywinauto/graphs/contributors
# http://pywinauto.readthedocs.io/en/latest/credits.html
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice, this
#   list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright notice,
#   this list of conditions and the following disclaimer in the documentation
#   and/or other materials provided with the distribution.
#
# * Neither the name of pywinauto nor the names of its
#   contributors may be used to endorse or promote products derived from
#   this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

"""Keyboard input emulation module

Automate typing keys to an active window by calling ``SendKeys`` method.
You can use any Unicode characters (on Windows) and some special keys listed
below. The module is also available on Linux.

**Available key codes:** ::

    {SCROLLLOCK}, {VK_SPACE}, {VK_LSHIFT}, {VK_PAUSE}, {VK_MODECHANGE},
    {BACK}, {VK_HOME}, {F23}, {F22}, {F21}, {F20}, {VK_HANGEUL}, {VK_KANJI},
    {VK_RIGHT}, {BS}, {HOME}, {VK_F4}, {VK_ACCEPT}, {VK_F18}, {VK_SNAPSHOT},
    {VK_PA1}, {VK_NONAME}, {VK_LCONTROL}, {ZOOM}, {VK_ATTN}, {VK_F10}, {VK_F22},
    {VK_F23}, {VK_F20}, {VK_F21}, {VK_SCROLL}, {TAB}, {VK_F11}, {VK_END},
    {LEFT}, {VK_UP}, {NUMLOCK}, {VK_APPS}, {PGUP}, {VK_F8}, {VK_CONTROL},
    {VK_LEFT}, {PRTSC}, {VK_NUMPAD4}, {CAPSLOCK}, {VK_CONVERT}, {VK_PROCESSKEY},
    {ENTER}, {VK_SEPARATOR}, {VK_RWIN}, {VK_LMENU}, {VK_NEXT}, {F1}, {F2},
    {F3}, {F4}, {F5}, {F6}, {F7}, {F8}, {F9}, {VK_ADD}, {VK_RCONTROL},
    {VK_RETURN}, {BREAK}, {VK_NUMPAD9}, {VK_NUMPAD8}, {RWIN}, {VK_KANA},
    {PGDN}, {VK_NUMPAD3}, {DEL}, {VK_NUMPAD1}, {VK_NUMPAD0}, {VK_NUMPAD7},
    {VK_NUMPAD6}, {VK_NUMPAD5}, {DELETE}, {VK_PRIOR}, {VK_SUBTRACT}, {HELP},
    {VK_PRINT}, {VK_BACK}, {CAP}, {VK_RBUTTON}, {VK_RSHIFT}, {VK_LWIN}, {DOWN},
    {VK_HELP}, {VK_NONCONVERT}, {BACKSPACE}, {VK_SELECT}, {VK_TAB}, {VK_HANJA},
    {VK_NUMPAD2}, {INSERT}, {VK_F9}, {VK_DECIMAL}, {VK_FINAL}, {VK_EXSEL},
    {RMENU}, {VK_F3}, {VK_F2}, {VK_F1}, {VK_F7}, {VK_F6}, {VK_F5}, {VK_CRSEL},
    {VK_SHIFT}, {VK_EREOF}, {VK_CANCEL}, {VK_DELETE}, {VK_HANGUL}, {VK_MBUTTON},
    {VK_NUMLOCK}, {VK_CLEAR}, {END}, {VK_MENU}, {SPACE}, {BKSP}, {VK_INSERT},
    {F18}, {F19}, {ESC}, {VK_MULTIPLY}, {F12}, {F13}, {F10}, {F11}, {F16},
    {F17}, {F14}, {F15}, {F24}, {RIGHT}, {VK_F24}, {VK_CAPITAL}, {VK_LBUTTON},
    {VK_OEM_CLEAR}, {VK_ESCAPE}, {UP}, {VK_DIVIDE}, {INS}, {VK_JUNJA},
    {VK_F19}, {VK_EXECUTE}, {VK_PLAY}, {VK_RMENU}, {VK_F13}, {VK_F12}, {LWIN},
    {VK_DOWN}, {VK_F17}, {VK_F16}, {VK_F15}, {VK_F14}

**Modifiers:**

- ``'+': {VK_SHIFT}``
- ``'^': {VK_CONTROL}``
- ``'%': {VK_MENU}`` a.k.a. Alt key

Example how to use modifiers: ::

    SendKeys('^a^c') # select all (Ctrl+A) and copy to clipboard (Ctrl+C)
    SendKeys('+{INS}') # insert from clipboard (Shift+Ins)
    SendKeys('%{F4}') # close an active window with Alt+F4

Repetition count can be specified for special keys. ``{ENTER 2}`` says to
press Enter twice.
"""
from __future__ import unicode_literals

import sys

if sys.platform != 'win32':
    from .linux.keyboard import KeySequenceError, KeyAction, PauseAction
    from .linux.keyboard import handle_code, parse_keys, SendKeys
else:
    import time
    import ctypes
    import win32api
    import six

    from . import win32structures

    __all__ = ['KeySequenceError', 'SendKeys']

    #pylint: disable-msg=R0903

    DEBUG = 0

    GetMessageExtraInfo = ctypes.windll.user32.GetMessageExtraInfo
    MapVirtualKey = ctypes.windll.user32.MapVirtualKeyW
    SendInput = ctypes.windll.user32.SendInput
    UINT = ctypes.c_uint
    SendInput.restype = UINT
    SendInput.argtypes = [UINT, ctypes.c_void_p, ctypes.c_int]

    VkKeyScan = ctypes.windll.user32.VkKeyScanW
    VkKeyScan.restype = ctypes.c_short
    VkKeyScan.argtypes = [ctypes.c_wchar]


    INPUT_KEYBOARD = 1
    KEYEVENTF_EXTENDEDKEY = 1
    KEYEVENTF_KEYUP       = 2
    KEYEVENTF_UNICODE     = 4
    KEYEVENTF_SCANCODE    = 8
    VK_SHIFT        = 16
    VK_CONTROL      = 17
    VK_MENU         = 18

    # 'codes' recognized as {CODE( repeat)?}
    CODES = {
        'BACK':     8,
        'BACKSPACE':8,
        'BKSP':     8,
        'BREAK':    3,
        'BS':       8,
        'CAP':      20,
        'CAPSLOCK': 20,
        'DEL':      46,
        'DELETE':   46,
        'DOWN':     40,
        'END':      35,
        'ENTER':    13,
        'ESC':      27,
        'F1':       112,
        'F2':       113,
        'F3':       114,
        'F4':       115,
        'F5':       116,
        'F6':       117,
        'F7':       118,
        'F8':       119,
        'F9':       120,
        'F10':      121,
        'F11':      122,
        'F12':      123,
        'F13':      124,
        'F14':      125,
        'F15':      126,
        'F16':      127,
        'F17':      128,
        'F18':      129,
        'F19':      130,
        'F20':      131,
        'F21':      132,
        'F22':      133,
        'F23':      134,
        'F24':      135,
        'HELP':     47,
        'HOME':     36,
        'INS':      45,
        'INSERT':   45,
        'LEFT':     37,
        'LWIN':     91,
        'NUMLOCK':  144,
        'PGDN':     34,
        'PGUP':     33,
        'PRTSC':    44,
        'RIGHT':    39,
        'RMENU':    165,
        'RWIN':     92,
        'SCROLLLOCK':145,
        'SPACE':     32,
        'TAB':       9,
        'UP':        38,

        'VK_ACCEPT': 30,
        'VK_ADD':    107,
        'VK_APPS':    93,
        'VK_ATTN':    246,
        'VK_BACK':    8,
        'VK_CANCEL':  3,
        'VK_CAPITAL': 20,
        'VK_CLEAR':   12,
        'VK_CONTROL': 17,
        'VK_CONVERT': 28,
        'VK_CRSEL':   247,
        'VK_DECIMAL': 110,
        'VK_DELETE':  46,
        'VK_DIVIDE':  111,
        'VK_DOWN':    40,
        'VK_END':     35,
        'VK_EREOF':   249,
        'VK_ESCAPE':  27,
        'VK_EXECUTE': 43,
        'VK_EXSEL':   248,
        'VK_F1':      112,
        'VK_F2':      113,
        'VK_F3':      114,
        'VK_F4':      115,
        'VK_F5':      116,
        'VK_F6':      117,
        'VK_F7':      118,
        'VK_F8':      119,
        'VK_F9':      120,
        'VK_F10':     121,
        'VK_F11':     122,
        'VK_F12':     123,
        'VK_F13':     124,
        'VK_F14':     125,
        'VK_F15':     126,
        'VK_F16':     127,
        'VK_F17':     128,
        'VK_F18':     129,
        'VK_F19':     130,
        'VK_F20':     131,
        'VK_F21':     132,
        'VK_F22':     133,
        'VK_F23':     134,
        'VK_F24':     135,
        'VK_FINAL':   24,
        'VK_HANGEUL':  21,
        'VK_HANGUL':   21,
        'VK_HANJA':    25,
        'VK_HELP':     47,
        'VK_HOME':     36,
        'VK_INSERT':   45,
        'VK_JUNJA':    23,
        'VK_KANA':     21,
        'VK_KANJI':    25,
        'VK_LBUTTON':   1,
        'VK_LCONTROL':162,
        'VK_LEFT':     37,
        'VK_LMENU':   164,
        'VK_LSHIFT':  160,
        'VK_LWIN':     91,
        'VK_MBUTTON':    4,
        'VK_MENU':        18,
        'VK_MODECHANGE':  31,
        'VK_MULTIPLY':   106,
        'VK_NEXT':        34,
        'VK_NONAME':     252,
        'VK_NONCONVERT':  29,
        'VK_NUMLOCK':    144,
        'VK_NUMPAD0':     96,
        'VK_NUMPAD1':     97,
        'VK_NUMPAD2':     98,
        'VK_NUMPAD3':     99,
        'VK_NUMPAD4':    100,
        'VK_NUMPAD5':    101,
        'VK_NUMPAD6':    102,
        'VK_NUMPAD7':    103,
        'VK_NUMPAD8':    104,
        'VK_NUMPAD9':    105,
        'VK_OEM_CLEAR':  254,
        'VK_PA1':        253,
        'VK_PAUSE':       19,
        'VK_PLAY':       250,
        'VK_PRINT':       42,
        'VK_PRIOR':       33,
        'VK_PROCESSKEY': 229,
        'VK_RBUTTON':      2,
        'VK_RCONTROL':   163,
        'VK_RETURN':      13,
        'VK_RIGHT':       39,
        'VK_RMENU':      165,
        'VK_RSHIFT':     161,
        'VK_RWIN':        92,
        'VK_SCROLL':     145,
        'VK_SELECT':      41,
        'VK_SEPARATOR':  108,
        'VK_SHIFT':       16,
        'VK_SNAPSHOT':    44,
        'VK_SPACE':       32,
        'VK_SUBTRACT':   109,
        'VK_TAB':          9,
        'VK_UP':          38,
        'ZOOM':          251,
    }
    # reverse the CODES dict to make it easy to look up a particular code name
    CODE_NAMES = dict((entry[1], entry[0]) for entry in CODES.items())

    # modifier keys
    MODIFIERS = {
        '+': VK_SHIFT,
        '^': VK_CONTROL,
        '%': VK_MENU,
    }


    class KeySequenceError(Exception):

        """Exception raised when a key sequence string has a syntax error"""

        def __str__(self):
            return ' '.join(self.args)


    class KeyAction(object):

        """Class that represents a single keyboard action

        It represents either a PAUSE action (not really keyboard) or a keyboard
        action (press or release or both) of a particular key.
        """

        def __init__(self, key, down = True, up = True):
            self.key = key
            if isinstance(self.key, six.string_types):
                self.key = six.text_type(key)
            self.down = down
            self.up = up

        def _get_key_info(self):
            """Return virtual_key, scan_code, and flags for the action

            This is one of the methods that will be overridden by sub classes.
            """
            return 0, ord(self.key), KEYEVENTF_UNICODE

        def get_key_info(self):
            """Return virtual_key, scan_code, and flags for the action

            This is one of the methods that will be overridden by sub classes.
            """
            return self._get_key_info()

        def GetInput(self):
            """Build the INPUT structure for the action"""
            actions = 1
            # if both up and down
            if self.up and self.down:
                actions = 2

            inputs = (win32structures.INPUT * actions)()

            vk, scan, flags = self._get_key_info()

            for inp in inputs:
                inp.type = INPUT_KEYBOARD

                inp.ki.wVk = vk
                inp.ki.wScan = scan
                inp.ki.dwFlags |= flags
                
                # it seems to return 0 every time but it's required by MSDN specification
                # so call it just in case
                inp.ki.dwExtraInfo = GetMessageExtraInfo()

            # if we are releasing - then let it up
            if self.up:
                inputs[-1].ki.dwFlags |= KEYEVENTF_KEYUP

            return inputs

        def run(self):
            """Execute the action"""
            inputs = self.GetInput()

            # SendInput() supports all Unicode symbols
            num_inserted_events = SendInput(len(inputs), ctypes.byref(inputs),
                                            ctypes.sizeof(win32structures.INPUT))
            if num_inserted_events != len(inputs):
                raise RuntimeError('SendInput() inserted only ' + str(num_inserted_events) +
                                   ' out of ' + str(len(inputs)) + ' keyboard events')

        def _get_down_up_string(self):
            """Return a string that will show whether the string is up or down
            
            return 'down' if the key is a press only
            return 'up' if the key is up only
            return '' if the key is up & down (as default)
            """
            down_up = ""
            if not (self.down and self.up):
                if self.down:
                    down_up = "down"
                elif self.up:
                    down_up = "up"
            return down_up
        
        def key_description(self):
            """Return a description of the key"""
            vk, scan, flags = self._get_key_info()
            desc = ''
            if vk:
                if vk in CODE_NAMES:
                    desc = CODE_NAMES[vk]
                else:
                    desc = "VK {}".format(vk)
            else:
                desc = "{}".format(self.key)
            
            return desc

        def __str__(self):
            parts = []
            parts.append(self.key_description())
            up_down = self._get_down_up_string()
            if up_down:
                parts.append(up_down)

            return "<{}>".format(" ".join(parts))
        __repr__ = __str__


    class VirtualKeyAction(KeyAction):

        """Represents a virtual key action e.g. F9 DOWN, etc

        Overrides necessary methods of KeyAction
        """

        def _get_key_info(self):
            """Virtual keys have extended flag set"""
            # copied more or less verbatim from
            # http://www.pinvoke.net/default.aspx/user32.sendinput
            if 33 <= self.key <= 46 or 91 <= self.key <= 93:
                flags = KEYEVENTF_EXTENDEDKEY
            else:
                flags = 0
            # This works for %{F4} - ALT + F4
            #return self.key, 0, 0

            # this works for Tic Tac Toe i.e. +{RIGHT} SHIFT + RIGHT
            return self.key, MapVirtualKey(self.key, 0), flags

        def run(self):
            """Execute the action"""
            # it works more stable for virtual keys than SendInput
            for inp in self.GetInput():
                win32api.keybd_event(inp.ki.wVk, inp.ki.wScan, inp.ki.dwFlags)


    class EscapedKeyAction(KeyAction):

        """Represents an escaped key action e.g. F9 DOWN, etc

        Overrides necessary methods of KeyAction
        """

        def _get_key_info(self):
            """EscapedKeyAction doesn't send it as Unicode

            The vk and scan code are generated differently.
            """
            vkey_scan = LoByte(VkKeyScan(self.key))

            return (vkey_scan, MapVirtualKey(vkey_scan, 0), 0)

        def key_description(self):
            """Return a description of the key"""
            return "KEsc {}".format(self.key)

        def run(self):
            """Execute the action"""
            # it works more stable for virtual keys than SendInput
            for inp in self.GetInput():
                win32api.keybd_event(inp.ki.wVk, inp.ki.wScan, inp.ki.dwFlags)


    class PauseAction(KeyAction):

        """Represents a pause action"""

        def __init__(self, how_long):
            self.how_long = how_long

        def run(self):
            """Pause for the lenght of time specified"""
            time.sleep(self.how_long)

        def __str__(self):
            return "<PAUSE %1.2f>"% (self.how_long)
        __repr__ = __str__


    def handle_code(code):
        """Handle a key or sequence of keys in braces"""
        code_keys = []
        # it is a known code (e.g. {DOWN}, {ENTER}, etc)
        if code in CODES:
            code_keys.append(VirtualKeyAction(CODES[code]))

        # it is an escaped modifier e.g. {%}, {^}, {+}
        elif len(code) == 1:
            code_keys.append(KeyAction(code))

        # it is a repetition or a pause  {DOWN 5}, {PAUSE 1.3}
        elif ' ' in code:
            to_repeat, count = code.rsplit(None, 1)
            if to_repeat == "PAUSE":
                try:
                    pause_time = float(count)
                except ValueError:
                    raise KeySequenceError('invalid pause time %s'% count)
                code_keys.append(PauseAction(pause_time))

            else:
                try:
                    count = int(count)
                except ValueError:
                    raise KeySequenceError(
                        'invalid repetition count {}'.format(count))

                # If the value in to_repeat is a VK e.g. DOWN
                # we need to add the code repeated
                if to_repeat in CODES:
                    code_keys.extend(
                        [VirtualKeyAction(CODES[to_repeat])] * count)
                # otherwise parse the keys and we get back a KeyAction
                else:
                    to_repeat = parse_keys(to_repeat)
                    if isinstance(to_repeat, list):
                        keys = to_repeat * count
                    else:
                        keys = [to_repeat] * count
                    code_keys.extend(keys)
        else:
            raise RuntimeError("Unknown code: {}".format(code))

        return code_keys


    def parse_keys(string,
                   with_spaces = False,
                   with_tabs = False,
                   with_newlines = False,
                   modifiers = None):
        """Return the parsed keys"""
        keys = []
        if not modifiers:
            modifiers = []
        index = 0
        while index < len(string):

            c = string[index]
            index += 1
            # check if one of CTRL, SHIFT, ALT has been pressed
            if c in MODIFIERS.keys():
                modifier = MODIFIERS[c]
                # remember that we are currently modified
                modifiers.append(modifier)
                # hold down the modifier key
                keys.append(VirtualKeyAction(modifier, up = False))
                if DEBUG:
                    print("MODS+", modifiers)
                continue

            # Apply modifiers over a bunch of characters (not just one!)
            elif c == "(":
                # find the end of the bracketed text
                end_pos = string.find(")", index)
                if end_pos == -1:
                    raise KeySequenceError('`)` not found')
                keys.extend(
                    parse_keys(string[index:end_pos], modifiers = modifiers))
                index = end_pos + 1

            # Escape or named key
            elif c == "{":
                # We start searching from index + 1 to account for the case {}}
                end_pos = string.find("}", index+1)
                if end_pos == -1:
                    raise KeySequenceError('`}` not found')

                code = string[index:end_pos]
                index = end_pos + 1
                keys.extend(handle_code(code))

            # unmatched ")"
            elif c == ')':
                raise KeySequenceError('`)` should be preceeded by `(`')

            # unmatched "}"
            elif c == '}':
                raise KeySequenceError('`}` should be preceeded by `{`')

            # so it is a normal character
            else:
                # don't output white space unless flags to output have been set
                if (c == ' ' and not with_spaces or \
                        c == '\t' and not with_tabs or \
                        c == '\n' and not with_newlines):
                    continue

                # output newline
                if c in ('~', '\n'):
                    keys.append(VirtualKeyAction(CODES["ENTER"]))

                # safest are the virtual keys - so if our key is a virtual key
                # use a VirtualKeyAction
                #if ord(c) in CODE_NAMES:
                #    keys.append(VirtualKeyAction(ord(c)))

                elif modifiers:
                    keys.append(EscapedKeyAction(c))

                else:
                    keys.append(KeyAction(c))

            # as we have handled the text - release the modifiers
            while modifiers:
                if DEBUG:
                    print("MODS-", modifiers)
                keys.append(VirtualKeyAction(modifiers.pop(), down = False))

        # just in case there were any modifiers left pressed - release them
        while modifiers:
            keys.append(VirtualKeyAction(modifiers.pop(), down = False))

        return keys

    def LoByte(val):
        """Return the low byte of the value"""
        return val & 0xff

    def HiByte(val):
        """Return the high byte of the value"""
        return (val & 0xff00) >> 8

    def SendKeys(keys,
                 pause=0.05,
                 with_spaces=False,
                 with_tabs=False,
                 with_newlines=False,
                 turn_off_numlock=True):
        """Parse the keys and type them"""
        keys = parse_keys(keys, with_spaces, with_tabs, with_newlines)

        for k in keys:
            k.run()
            time.sleep(pause)
